﻿using System;
using System.Diagnostics;
using System.Xml;
using Api = Authorit.API;

namespace DitaImport
{
	/// <summary>
	/// Represents a DITA topic to import into Author-it
	/// </summary>
	public class DitaTopic
	{
		#region Fields (5) 

		/// <summary>
		/// Stores the name of the file from which we import the topic
		/// </summary>
		private string fileName;
		/// <summary>
		/// Manages importing the topic
		/// </summary>
		private Importer importer;
		/// <summary>
		/// Stores the topic text XML
		/// </summary>
		private TextBlocks textBlocks = new TextBlocks();
		/// <summary>
		/// Manages the topic in Author-it
		/// </summary>
		private Api.Dto.Topic topic;
		/// <summary>
		/// Specifies whether to create a subheading in the topic for the &lt;title&gt; element value
		/// </summary>
		/// <value>true to create a subheading in the topic; otherwise, false</value>
		private bool useTitleForSubHeading;

		#endregion Fields 

		#region Constructors (1) 

		/// <summary>
		/// Initializes a new instance of the <see cref="DitaTopic"/> class.
		/// </summary>
		/// <param name="Import">Importer that manages this <see cref="DitaTopic"/></param>
		internal DitaTopic(Importer Import)
		{
			importer = Import;  //Reference the importer
			textBlocks = new TextBlocks();  //Initialize the topic text XML
		}

		#endregion Constructors 

		#region Delegates and Events (6) 

		// Delegates (3) 

		/// <summary>
		/// Represents the method that will handle the <see cref="Log"/> event
		/// </summary>
		/// <param name="e">Arguments for the event</param>
		public delegate void LogEventHandler(LogEventArgs e);
		/// <summary>
		/// Represents the method that will handle the <see cref="ObjectCreated"/> event
		/// </summary>
		/// <param name="ObjectId">ID of the new object</param>
		public delegate void ObjectCreatedEventHandler(int ObjectId);
		/// <summary>
		/// Represents the method that will handle the <see cref="ObjectModified"/> event
		/// </summary>
		/// <param name="ObjectId">ID of the modified object</param>
		public delegate void ObjectModifiedEventHandler(int ObjectId);
		// Events (3) 

		/// <summary>
		/// Occurs when the plugin logs an event
		/// </summary>
		public event LogEventHandler Log;
		/// <summary>
		/// Occurs when the plugin creates an Author-it object
		/// </summary>
		public event ObjectCreatedEventHandler ObjectCreated;
		/// <summary>
		/// Occurs when the plugin modifies an Author-it object
		/// </summary>
		public event ObjectModifiedEventHandler ObjectModified;

		#endregion Delegates and Events 

		#region Methods (15) 

		// Public Methods (1) 

		/// <summary>
		/// Returns the XML namespace manager object for the specified document
		/// </summary>
		/// <param name="xmlDocument">Document that contains namespaces</param>
		/// <returns><see cref="XmlNamespaceManager"/> object that contains the namespaces in <paramref name="xmlDocument"/> and the Author-it XML namespace</returns>
		public XmlNamespaceManager GetXmlNamespaceManager(System.Xml.XmlDocument xmlDocument)
		{
			System.Xml.XmlNamespaceManager XmlNamespaceManager = new System.Xml.XmlNamespaceManager(xmlDocument.NameTable);  //Initialize the namespace manager
			XmlNamespaceManager.AddNamespace(importer.ConfigSettings.AitXmlNamespacePrefix, importer.ConfigSettings.AitXmlNamespace);  //Add the Author-it namespace
			return XmlNamespaceManager;  //Return the namespace manager
		}
		// Private Methods (13) 

		/// <summary>
		/// Creates a new table cell and adds it to the specified text block
		/// </summary>
		/// <param name="textBlocks">Text blocks to which to add the new table cell</param>
		/// <param name="xmlNode"></param>
		/// <returns>Text block of type </returns>
		private TextBlock AddCell(ref TextBlocks textBlocks, XmlNode xmlNode)
		{
			TextBlock textBlock = new TextBlock(TextBlock.TextBlockType.Cell);
			textBlocks.Add(textBlock);
			return textBlock;
		}

		private TextBlock AddEmbeddedTopic(ref TextBlocks TextBlocks, int ParagraphStyleId, int TopicId)
		{
			TextBlock textBlock = new TextBlock(TextBlock.TextBlockType.Paragraph);
			textBlock.XmlText = string.Format("<p id=\"{0}\" ><tref id=\"{1}\" /></p>", ParagraphStyleId, TopicId);
			TextBlocks.Add(textBlock);
			return textBlock;
		}

		private TextBlock AddHypertextLink(ref TextBlocks TextBlocks, XmlNode XmlNode, int ParagraphStyleId)
		{
			string text = XmlNode.InnerText;
			text = System.Security.SecurityElement.Escape(XmlNode.InnerText);
			TextBlock textBlock = new TextBlock(TextBlock.TextBlockType.Paragraph);
			textBlock.XmlText = string.Format("<p id=\"{0}\" >HLINK: {1}</p>", ParagraphStyleId, text);
			TextBlocks.Add(textBlock);
			return textBlock;
		}

		private TextBlock AddParagraph(ref TextBlocks TextBlocks, string Text, int ParagraphStyleId)
		{
			TextBlock textBlock = new TextBlock(TextBlock.TextBlockType.Paragraph);
			textBlock.XmlText = string.Format("<p id=\"{0}\" >", ParagraphStyleId) + System.Security.SecurityElement.Escape(Text) + "</p>";

			TextBlocks.Add(textBlock);
			return textBlock;
		}

		private TextBlock AddParagraph(ref TextBlocks TextBlocks, XmlNode XmlNode, int ParagraphStyleId, string ParagraphPrefix = "")
		{
			System.Text.StringBuilder sb = new System.Text.StringBuilder();

			if (ParagraphPrefix.Length > 0)
			{
				sb.Append(ParagraphPrefix);
			}
			if (XmlNode.ChildNodes.Count == 0)
			{
				sb.Append(System.Security.SecurityElement.Escape(XmlNode.InnerText));
			}
			else
			{
				foreach (XmlNode childNode in XmlNode.ChildNodes)
				{
					string nodeName = childNode.Name;

					if (nodeName == "#text")
					{
						sb.Append(System.Security.SecurityElement.Escape(childNode.InnerText));
					}
					else if (nodeName == "#whitespace")
					{
						sb.Append(" ");
					}
					else if (nodeName == "#significant-whitespace")
					{
						sb.Append(System.Security.SecurityElement.Escape(childNode.InnerText));
					}
					else if (Properties.Settings.Default.ConceptLink.Contains(nodeName))
					{
						//TODO: not tested for DITA - rewrite
						// link, legacyLink
						Debug.Assert(false);

						//Dim anchorText As String = System.Security.SecurityElement.Escape(childNode.InnerText)
						//Dim href As String = childNode.Attributes("xlink:href").InnerText

						//Try
						//    Dim hyperlinkBrief As API.Dto.ObjectBrief = importer.GetHypertextLinkObjectBriefFromGuid(New Guid(href.ToString))
						//    sb.AppendFormat("<href id=""{0}"" >{1}</href>", hyperlinkBrief.ObjectId, anchorText)
						//Catch ex As Exception
						//    'TODO: no href???
						//    sb.Append("[link]" & anchorText & "-->" & href & "[/link]")
						//End Try
					}
					else if (Properties.Settings.Default.ConceptExternalLink.Contains(nodeName))
					{
						//TODO: not tested for DITA - rewrite
						Debug.Assert(false);

						// externalLink
						//Dim anchorText As String = childNode.Item("linkText").InnerText
						//Dim href As String = childNode.Item("linkUri").InnerText

						//Try
						//    Dim hyperlinkBrief As API.Dto.ObjectBrief = importer.GetHypertextExternalLinkObjectBriefFromGuid(href, anchorText)
						//    sb.AppendFormat("<href id=""{0}"" >{1}</href>", hyperlinkBrief.ObjectId, anchorText)
						//Catch ex As Exception
						//    'TODO: no href???
						//    sb.Append("[externalLink]" & anchorText & "-->" & href & "[/externalLink]")
						//End Try
					}
					else if (Properties.Settings.Default.ConceptInlineBold.Contains(nodeName))
					{
						//TODO: not tested for DITA - need to add appropriate tags to app.config
						if (childNode.InnerText.Length > 0)
						{
							//legacyBold
							sb.AppendFormat("<cs id=\"{0}\">{1}</cs>", importer.GetObjectId(Properties.Settings.Default.StyleCharacterBold), System.Security.SecurityElement.Escape(childNode.InnerText));
						}
					}
					else if (Properties.Settings.Default.ConceptInlineCode.Contains(nodeName))
					{
						//TODO: not tested for DITA - need to add appropriate tags to app.config
						if (childNode.InnerText.Length > 0)
						{
							// codeInline, parameterReference, legacySyntax, languageKeyword
							sb.AppendFormat("<cs id=\"{0}\">{1}</cs>", importer.GetObjectId(Properties.Settings.Default.StyleCharacterMonoSpace), System.Security.SecurityElement.Escape(childNode.InnerText));
						}
					}
					else if (Properties.Settings.Default.ConceptInlineItalic.Contains(nodeName))
					{
						if (childNode.InnerText.Length > 0)
						{
							//legacyItalic
							sb.AppendFormat("<cs id=\"{0}\">{1}</cs>", importer.GetObjectId(Properties.Settings.Default.StyleCharacterItalic), System.Security.SecurityElement.Escape(childNode.InnerText));
						}
					}
					else if (Properties.Settings.Default.ConceptTable.Contains(nodeName))
					{
						// A ConceptTable match indicates we've hit the root node of a table
						// Set in app.config: table, simpletable
						MapNodeTable(ref TextBlocks, childNode, false);
					}
					else if (Properties.Settings.Default.ConceptProcessChildNodes.Contains(nodeName))
					{
						// This node has no actual text, it just wraps other data, just import the child nodes
						// Set in app.config: body
						MapNodes(ref TextBlocks, childNode, false);
					}
					else
					{
						Debug.Print("Tag <" + nodeName + "> not recognised in topic " + topic.ObjectId);
						sb.Append("[" + childNode.Name + "]" + System.Security.SecurityElement.Escape(childNode.InnerText) + "[/" + childNode.Name + "]");
					}
				}
			}

			if (sb.Length > 0)
			{
				TextBlock textBlock = new TextBlock(TextBlock.TextBlockType.Paragraph);
				textBlock.XmlText = string.Format("<p id=\"{0}\" >", ParagraphStyleId) + sb.ToString() + "</p>";

				TextBlocks.Add(textBlock);
				return textBlock;
			}
			else
			{
				return null;
			}
		}

		private TextBlock AddRow(ref TextBlocks TextBlocks, XmlNode XmlNode)
		{
			TextBlock textBlock = new TextBlock(TextBlock.TextBlockType.Row);

			try
			{
				if (XmlNode.ParentNode.Name == "tableHeader")
				{
					textBlock.IsHeaderRow = true;
				}
			}
			catch (Exception ex)
			{
				// we can ignore the error - the 'Try' above is a bit of a hack to figure out if we're in a header row
			}

			TextBlocks.Add(textBlock);
			return textBlock;
		}

		private TextBlock AddTable(ref TextBlocks TextBlocks, XmlNode XmlNode)
		{
			TextBlock textBlock = new TextBlock(TextBlock.TextBlockType.Table);
			TextBlocks.Add(textBlock);
			return textBlock;
		}

		private bool InList(System.Xml.XmlNode XmlNode, ref int StyleId)
		{
			// Walks back up the xml tree looking for a node type that indicates absolutely
			// that it IS or ISN'T in a list
			// StyleId is passed BYREF and is modified by the function.
			if (Properties.Settings.Default.ConceptTable.Contains(XmlNode.Name))
			{
				// if we hit a tag that indicates a table / row or cell we're not in a list
				return false;
			}
			else
			{
				switch (XmlNode.Name)
				{
					case "para":
					case "#whitespace":
					case "#text":
						return InList(XmlNode.ParentNode, ref StyleId);
					case "list":
					case "steps":

						// we are in a list
						try
						{
							if (XmlNode.Attributes.GetNamedItem("class").Value == "bullet")
							{
								StyleId = importer.GetObjectId(Properties.Settings.Default.StyleListBullet);
							}
							else
							{
								StyleId = importer.GetObjectId(Properties.Settings.Default.StyleListNumber);
							}
						}
						catch (Exception ex)
						{
							StyleId = importer.GetObjectId(Properties.Settings.Default.StyleListBullet);
						}
						return true;
					case "title":
					case "introduction":
					case "conclusion":

						// we are not in a list
						return false;
					default:
						if (XmlNode.ParentNode != null)
						{
							return InList(XmlNode.ParentNode, ref StyleId);
						}
						else // we've hit the root node, we aren't in a list
						{
							return false;
						}
				}
			}
		}

		/// <summary>
		/// This is the catch all MapNode function that can be called with any Node
		/// It will in turn feed data on to specific MapNode functions like:
		/// - MapNodePara()
		/// - MapNodeTable()
		/// </summary>
		/// <param name="textBlocks"></param>
		/// <param name="xmlNode"></param>
		/// <param name="isHeaderRow"></param>
		/// <returns></returns>
		/// <remarks></remarks>
		private bool MapNode(ref TextBlocks textBlocks, System.Xml.XmlNode xmlNode, bool isHeaderRow)
		{
			// get the name of this node
			string nodeName = xmlNode.Name;
			if (Properties.Settings.Default.ConceptSubHeading.Contains(nodeName))
			{
				// A ConceptSubHeading tag indicates the tag should be the topic heading (or a subheading if it is already set)
				// Set in app.config: title, example
				MapNodeTitle(ref textBlocks, xmlNode, isHeaderRow);
			}
			else if (Properties.Settings.Default.ConceptTable.Contains(nodeName))
			{
				// A ConceptTable match indicates we've hit the root node of a table
				// Set in app.config: table, simpletable
				return MapNodeTable(ref textBlocks, xmlNode, isHeaderRow);
			}
			else if (importer.ConfigSettings.ElementsTopicContent.Contains(nodeName))
			{
				// A BodyText match indicates we add a paragraph
				// Set in app.config: shortdesc, p
				AddParagraph(ref textBlocks, xmlNode, importer.GetObjectId(Properties.Settings.Default.StyleBodyText));
			}
			else if (Properties.Settings.Default.ConceptProcessChildNodes.Contains(nodeName))
			{
				// This node has no actual text, it just wraps other data, just import the child nodes
				// Set in app.config: body, section
				MapNodes(ref textBlocks, xmlNode, isHeaderRow);
			}
			else if (Properties.Settings.Default.ConceptOrderedList.Contains(nodeName))
			{
				// This node has no actual text, it just wraps other data, just import the child nodes
				// Set in app.config: ol
				foreach (XmlNode childNode in xmlNode)
				{
					AddParagraph(ref textBlocks, childNode, importer.GetObjectId(Properties.Settings.Default.StyleListNumber));
				}
			}
			else if (Properties.Settings.Default.ConceptUnorderedList.Contains(nodeName))
			{
				// This node has no actual text, it just wraps other data, just import the child nodes
				// Set in app.config: ul
				foreach (XmlNode childNode in xmlNode)
				{
					AddParagraph(ref textBlocks, childNode, importer.GetObjectId(Properties.Settings.Default.StyleListBullet));
				}
			}
			else if (nodeName == "#whitespace")
			{
				//ignore for the moment
				//MapNodes(textBlocks, xmlNode, isHeaderRow)
				Debug.Print("whitespace skipped");
			}
			else if (nodeName == "#text")
			{
				AddParagraph(ref textBlocks, xmlNode, importer.GetObjectId(Properties.Settings.Default.StyleBodyText));
			}
			else if (nodeName == "topic")
			{
				// we have an embedded topic
				int embeddedTopicId = importer.GetEmbeddedTopicId(xmlNode, fileName);
				AddEmbeddedTopic(ref textBlocks, importer.GetObjectId(Properties.Settings.Default.StyleBodyText), embeddedTopicId);
			}
			else if (nodeName == "b")
			{
				//HACK: we've hit a character style before hitting a character style
				AddParagraph(ref textBlocks, xmlNode, importer.GetObjectId(Properties.Settings.Default.StyleBodyText));
			}
			else
			{
				// WE DON'T KNOW WHAT TO DO WITH THIS NODE = MORE DEVELOPMENT NEEDED.
				Debug.Print("unknown tag: " + nodeName + " in topic " + topic.ObjectId);
				if (Log != null)
					Log(new LogEventArgs(Importer.Severity.Warning, string.Format("MapNode: unknown tag <{0}> in topic ({1})", nodeName, topic.ObjectId)));
				if (xmlNode.HasChildNodes)
				{
					MapNodes(ref textBlocks, xmlNode, isHeaderRow);
				}
			}
			return true;
		}

		private void MapNodes(ref TextBlocks TextBlocks, System.Xml.XmlNode XmlNode, bool HeaderRow)
		{
			if (XmlNode.HasChildNodes)
			{
				foreach (XmlNode childNode in XmlNode.ChildNodes)
				{
					MapNode(ref TextBlocks, childNode, HeaderRow);
				}
			}
		}

		//Private Function IsNumbered(ByVal xmlNode As Xml.xmlNode) As Boolean
		//    ' There are several types of lists in DDUEML:
		//    ' ----------------------------------------
		//    ' <list class="bullet">
		//    '  <listitem> ... </listitem>
		//    '  <listitem> ... </listitem>
		//    ' </list>
		//    ' ----------------------------------------
		//    ' <list class="ordered">
		//    '  <listitem> ... </listitem>
		//    '  <listitem> ... </listitem>
		//    ' </list>
		//    ' ----------------------------------------
		//    ' <steps class="ordered">
		//    '   <step><content> ... </content></step>       <-- note the first <para> tag should have list number, subsequent ones should have list continue
		//    '   <step><content> ... </content></step>
		//    ' </steps>
		//    ' ----------------------------------------
		//    If xmlNode.Name = "list" OrElse xmlNode.Name = "steps" Then
		//        If xmlNode.Attributes.Count > 0 Then
		//            Try
		//                If xmlNode.Attributes.GetNamedItem("class").Value = "bullet" Then
		//                    Return False
		//                Else
		//                    Return True
		//                End If
		//            Catch ex As Exception
		//                Return False
		//            End Try
		//        Else
		//            Return True
		//        End If
		//    Else
		//        Return IsNumbered(xmlNode.ParentNode)
		//    End If
		//End Function
		//Private Function MapNodePara(ByRef textBlocks As textBlocks, ByVal xmlNode As Xml.xmlNode, ByVal isHeaderRow As Boolean) As Boolean
		//    'If xmlNode.Name = "para" Then
		//    ' find the context of the "para" node
		//    Dim paraContext As String = xmlNode.Name
		//    Dim paraAttributes As Xml.XmlAttributeCollection
		//    Select Case xmlNode.ParentNode.Name
		//        Case "#whitespace", "#text"
		//            paraContext = xmlNode.ParentNode.ParentNode.Name
		//            paraAttributes = xmlNode.ParentNode.ParentNode.Attributes
		//        Case Else
		//            paraContext = xmlNode.ParentNode.Name
		//            paraAttributes = xmlNode.ParentNode.Attributes
		//    End Select
		//    Select Case paraContext
		//        Case "introduction"
		//            AddParagraph(textBlocks, xmlNode, importer.GetObjectId(My.Settings.StyleBodyText))
		//        Case "alert"
		//            Try
		//                Dim paragraphPrefixXml As String = ""
		//                Select Case paraAttributes("class").Value
		//                    Case "note"
		//                        paragraphPrefixXml = String.Format("<cs id=""{0}"">Note:</cs> ", importer.GetObjectId(My.Settings.StyleCharacterLabel))
		//                        AddParagraph(textBlocks, xmlNode, importer.GetObjectId(My.Settings.StyleNote), paragraphPrefixXml)
		//                    Case "important"
		//                        paragraphPrefixXml = String.Format("<cs id=""{0}"">Note:</cs> ", importer.GetObjectId(My.Settings.StyleCharacterLabel))
		//                        AddParagraph(textBlocks, xmlNode, importer.GetObjectId(My.Settings.StyleImportant), paragraphPrefixXml)
		//                    Case Else
		//                        AddParagraph(textBlocks, xmlNode, importer.GetObjectId(My.Settings.StyleNote))
		//                End Select
		//            Catch ex As Exception
		//                ' probably missing the "class" attribute
		//                AddParagraph(textBlocks, xmlNode, importer.GetObjectId(My.Settings.StyleNote))
		//            End Try
		//        Case "definition"
		//            AddParagraph(textBlocks, xmlNode, importer.GetObjectId(My.Settings.StyleDefinition))
		//        Case "list", "listItem", "steps", "step", "content"
		//            Dim StyleId As Integer = importer.GetObjectId(My.Settings.StyleBodyText)
		//            If InList(xmlNode, StyleId) Then
		//                If xmlNode Is xmlNode.ParentNode.FirstChild Then
		//                    ' apply the appropriate list style
		//                    AddParagraph(textBlocks, xmlNode, StyleId)
		//                Else
		//                    ' apply 'skip numbering'
		//                    AddParagraph(textBlocks, xmlNode, importer.GetObjectId(My.Settings.StyleListContinue))
		//                End If
		//            Else
		//                AddParagraph(textBlocks, xmlNode, StyleId)
		//            End If
		//        Case "entry"
		//            If isHeaderRow Then
		//                AddParagraph(textBlocks, xmlNode, importer.GetObjectId(My.Settings.StyleTableHeading))
		//            Else
		//                AddParagraph(textBlocks, xmlNode, importer.GetObjectId(My.Settings.StyleTableBodyText))
		//            End If
		//        Case Else
		//            Debug.Print("Unknown para node: " & paraContext)
		//            ' the node context is unknown, just process the child nodes
		//            If xmlNode.ChildNodes.Count > 1 Then
		//                MapNodes(textBlocks, xmlNode, isHeaderRow)
		//            Else
		//                AddParagraph(textBlocks, xmlNode, importer.GetObjectId(My.Settings.StyleBodyText))
		//            End If
		//    End Select
		//    Return True
		//    'Else
		//    '    Return False
		//    'End If
		//End Function

		private bool MapNodeTable(ref TextBlocks textBlocks, System.Xml.XmlNode xmlNode, bool headerRow)
		{
			// Note: it's possible to have nested tables.

			string nodeName = xmlNode.Name;
			if (Properties.Settings.Default.ConceptTable.Contains(nodeName))
			{
				// The ConceptTable is the root node for any table
				// Set in app.config: table, simpletable
				TextBlock tableTextBlock = AddTable(ref textBlocks, xmlNode);
				foreach (XmlNode childNode in xmlNode.ChildNodes)
				{
					// Ensure the isHeaderRow parameter is cleared for the new (possibly nested) table
					MapNodeTable(ref tableTextBlock.ChildTextBlocks, childNode, false);
				}
			}
			else if (nodeName == "colspec")
			{
				//TODO: need to set column widths / attributes based on this
			}
			else if (Properties.Settings.Default.ConceptTableHeaderRowWrapper.Contains(nodeName))
			{
				// The ConceptTableHeaderRowWrapper is a tag that indicates its ChildNode is a table row
				// it can be used when the wrapper row only has data in child elements
				// For example: <tableHeader><row> ... </row><tableHeader>
				// DEFAULT DATA:
				foreach (XmlNode childNode in xmlNode.ChildNodes)
				{
					MapNodeTable(ref textBlocks, childNode, true);
				}
			}
			else if (Properties.Settings.Default.ConceptTableHeaderRow.Contains(nodeName))
			{
				// The ConceptTableHeaderRow is a tag that indicates a table header row
				// DEFAULT DATA: th, thhead, sthead
				// Add a row and process the child tags (which should be cells)
				TextBlock rowTextBlock = AddRow(ref textBlocks, xmlNode);
				foreach (XmlNode childNode in xmlNode.ChildNodes)
				{
					MapNodeTable(ref rowTextBlock.ChildTextBlocks, childNode, true);
				}
			}
			else if (Properties.Settings.Default.ConceptTableRow.Contains(nodeName))
			{
				// The ConceptTableRow is a tag that indicates a table row that isn't a header row
				// Set in app.config: tr, row, strow
				// Add a row and process the child tags (which should be cells)
				TextBlock rowTextBlock = AddRow(ref textBlocks, xmlNode);
				foreach (XmlNode childNode in xmlNode.ChildNodes)
				{
					MapNodeTable(ref rowTextBlock.ChildTextBlocks, childNode, headerRow);
				}
			}
			else if (Properties.Settings.Default.ConceptTableData.Contains(nodeName))
			{
				// The ConceptTableData is a tag that indicates table data (a cell)
				// Set in app.config: td, cell, entry, stentry
				TextBlock cellTextBlock = AddCell(ref textBlocks, xmlNode);
				foreach (XmlNode childNode in xmlNode.ChildNodes)
				{
					MapNode(ref cellTextBlock.ChildTextBlocks, childNode, headerRow);
				}
			}
			else if (Properties.Settings.Default.ConceptProcessChildNodes.Contains(nodeName))
			{
				// The ConceptProcessChildNodes indicates that the node itself has no meaning but the child nodes need to be processes
				// DEFAULT DATA: body
				foreach (XmlNode childNode in xmlNode.ChildNodes)
				{
					MapNodeTable(ref textBlocks, childNode, headerRow);
				}
			}
			else // hopefully we are in a cell and can start processing paragraph level data
			{
				MapNode(ref textBlocks, xmlNode, headerRow);
			}

			return true;
		}

		/// <summary>
		/// Maps a &lt;title&gt; element to the Author-it context
		/// </summary>
		/// <param name="textBlocks"></param>
		/// <param name="xmlNode"></param>
		/// <param name="isHeaderRow"></param>
		/// <remarks></remarks>
		private void MapNodeTitle(ref TextBlocks textBlocks, System.Xml.XmlNode xmlNode, bool isHeaderRow)
		{
			// 1. the first <title> type node sets the topic headings
			// 2. any subsequent <title> type nodes create a subheading

			// get the text
			string title = xmlNode.InnerText;

			// see if this is the first <title> node struck in this topic
			if (!useTitleForSubHeading)
			{
				// this is the first title node, set the topic headings
				topic.Description = title;
				topic.PrintHeading = title;
				topic.HelpHeading = title;
				topic.WebHeading = title;
				useTitleForSubHeading = true;
			}
			else
			{
				// this isn't the first <title> node, create a sub heading
				AddParagraph(ref textBlocks, xmlNode.InnerText, importer.GetObjectId(Properties.Settings.Default.StyleSubHeading));
			}

			//HACK: to avoid processing #text node
			// not sure if we need this - as it's unlikely that <title> nodes have child nodes
			if (xmlNode.InnerXml.Contains("<"))
			{
				MapNodes(ref textBlocks, xmlNode, isHeaderRow);
			}
		}

		//Private Sub MapRelatedTopics(ByVal RelatedTopicGroup As String, ByVal xmlNode As Xml.xmlNode)
		//    ' iterate through the child nodes adding the children to the appropriate related topic group
		//    If xmlNode.HasChildNodes Then
		//        ' TODO: move
		//        Dim fileObjectSpecialSection As API.Dto.ObjectBrief = importer.GetObjectBriefFromGuid(API.Dto.ObjectType.File, My.Settings.FileObjectSpecialSection, "", "minus")
		//        Dim styleSubHeading As API.Dto.ObjectBrief = importer.GetObjectBriefFromGuid(API.Dto.ObjectType.Style, My.Settings.StyleSubHeading, "", "Sub Heading")
		//        Dim styleBodyText As API.Dto.ObjectBrief = importer.GetObjectBriefFromGuid(API.Dto.ObjectType.Style, My.Settings.StyleBodyText, "", "Body Text")
		//        _sbRelatedTopics.AppendFormat("<RelatedGroup inherited=""false"">" & _
		//                                      "<Alignment>2</Alignment>" & _
		//                                      "<Children>false</Children>" & _
		//                                      "<ColumnWidth>(auto)</ColumnWidth>" & _
		//                                      "<Contents>false</Contents>" & _
		//                                      "<ExcludeHeadingText>false</ExcludeHeadingText>" & _
		//                                      "<Heading>{0}</Heading>" & _
		//                                      "<HeadingFile>{1}</HeadingFile>" & _
		//                                      "<HeadingStyle>{2}</HeadingStyle>" & _
		//                                      "<IncludeInHelp>true</IncludeInHelp>" & _
		//                                      "<IncludeInPrint>false</IncludeInPrint>" & _
		//                                      "<IncludeInWeb>true</IncludeInWeb>" & _
		//                                      "<Index>false</Index>" & _
		//                                      "<ItemStyle>{3}</ItemStyle>" & _
		//                                      "<NextTopic>false</NextTopic>" & _
		//                                      "<ParentTopic>false</ParentTopic>" & _
		//                                      "<PopUp>false</PopUp>" & _
		//                                      "<Position>3</Position>" & _
		//                                      "<PreviousTopic>false</PreviousTopic>" & _
		//                                      "<RelatedGroupID>{4}</RelatedGroupID>" & _
		//                                      "<Sequence>0</Sequence>" & _
		//                                      "<Siblings>false</Siblings>" & _
		//                                      "<SpecificLinks>", _
		//                                      RelatedTopicGroup, _
		//                                      fileObjectSpecialSection.ObjectId, _
		//                                      styleSubHeading.ObjectId, _
		//                                      styleBodyText.ObjectId, _
		//                                      RelatedTopicGroup & "|" & topic.ObjectId.ToString)
		//        _objectBriefs.Add(fileObjectSpecialSection)
		//        For Each childNode As xmlNode In xmlNode.ChildNodes
		//            If childNode.Name = "link" Then
		//                If childNode.Attributes IsNot Nothing AndAlso childNode.Attributes("xlink:href") IsNot Nothing Then
		//                    Try
		//                        Dim objectGuid As String = childNode.Attributes("xlink:href").Value
		//                        If objectGuid.Length = 36 Then
		//                            Dim objectDescription As String = childNode.InnerText
		//                            Dim targetObjectBrief As API.Dto.ObjectBrief = importer.GetObjectBriefFromGuid(API.Dto.ObjectType.Topic, New Guid(objectGuid), "", objectDescription)
		//                            If targetObjectBrief.ObjectId = topic.ObjectId Then
		//                                RaiseEvent Log(New ImportLogEventArgs(DITAImport.Severity.Warning, String.Format("Topic {0} has itself as a related topic", targetObjectBrief.ObjectId)))
		//                            Else
		//                                _sbRelatedTopics.AppendFormat("<Relation><ID>{0}</ID></Relation>", targetObjectBrief.ObjectId)
		//                                _objectBriefs.Add(targetObjectBrief)
		//                            End If
		//                        Else
		//                            RaiseEvent Log(New ImportLogEventArgs(DITAImport.Severity.Warning, "Cannot convert '" & objectGuid & "' to a guid."))
		//                        End If
		//                    Catch ex As Exception
		//                        Debug.Print("Error: " & childNode.InnerXml)
		//                    End Try
		//                End If
		//            Else
		//                Debug.Print("Cannot process: " & childNode.OuterXml)
		//            End If
		//        Next
		//        _sbRelatedTopics.Append("</SpecificLinks>" & _
		//                                "</RelatedGroup>")
		//    End If
		//End Sub

		private string ObjectBriefToObjectSummaryXml(Api.Dto.ObjectBrief ObjectSummary)
		{
			string objectSummaryXml = string.Format("<ObjectSummary>" + "<ID>{0}</ID>" + "<Type>{1}</Type>" + "<GUID>{2}</GUID>" + "<Description>{3}</Description>" + "</ObjectSummary>", ObjectSummary.ObjectId, ObjectSummary.ObjectType.ToString(), ObjectSummary.Guid.ToString().Replace("-", "").ToUpper(), ObjectSummary.Description);

			return objectSummaryXml;
		}
		// Internal Methods (1) 

		internal void ImportTopicText(int TopicId, System.Xml.XmlNode TopicNode, string Filename, string ImportId, bool EmbeddedTopic)
		{
			try
			{
				// load the topic data transfer object
				topic = importer.TopicService.Get(TopicId);

				// for non-embedded topics, we skip the first <title> node and put it in the Topic.Headings instead
				// but for embedded topics, we need to include a sub-heading and embed the topic instead
				useTitleForSubHeading = EmbeddedTopic;
				fileName = Filename;

				// load the topic text
				string topicTextBefore = importer.TopicService.GetTopicTextXml(TopicId);

				// parse the xmlDocument
				MapNodes(ref textBlocks, TopicNode, false);

				string topicTextAfter = textBlocks.ToString();

				if (topicTextBefore != topicTextAfter)
				{
					// the topic text has changed on re-import save it
					importer.TopicService.SaveTopicTextFromXml(TopicId, topicTextAfter);
					if (Log != null)
						Log(new LogEventArgs(Importer.Severity.Information, string.Format("Topic ({0}) '{1}' imported all from '{2}' ", TopicId, topic.Description, Filename)));
				}

				importer.CreateVariableAssignment(importer.VariableImportId, TopicId, ImportId);
				importer.CreateVariableAssignment(importer.VariableImportFullPath, TopicId, Filename);
			}
			catch (Exception ex)
			{
				//TODO: Log unable to save
				if (Log != null)
					Log(new LogEventArgs(Importer.Severity.Error, string.Format("Exception saving topic ({0}) '{1}'", topic.ObjectId, topic.Description), ex));
				throw new Exception(string.Format("Exception saving topic ({0}) '{1}'", topic.ObjectId, topic.Description), ex);
			}
		}

		#endregion Methods 
	}
}